package cn.zdsoft.configuration.core.web;

import cn.zdsoft.configuration.core.ConfigContext;
import cn.zdsoft.configuration.core.Constants;
import cn.zdsoft.configuration.core.exceptions.ConfigurationException;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringValueResolver;
import org.springframework.web.context.WebApplicationContext;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by wangdongdong on 2018/5/14.
 */
public class AbstractPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private String beanName;

    private Map<String, List<PropertyInfo>> beanPropertyMap = null;

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    @Override
    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
        String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();

        for (String curName : beanNames) {
            if (!curName.equals(this.beanName)) {
                BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);

                MutablePropertyValues pvs = bd.getPropertyValues();
                PropertyValue[] pvList = pvs.getPropertyValues();
                List<PropertyInfo> infos = new ArrayList<>();
                for (PropertyValue pv : pvList) {
                    Object o = pv.getValue();
                    if (o instanceof TypedStringValue) {
                        String tagVal = ((TypedStringValue) o).getValue();
                        if (tagVal.startsWith(this.placeholderPrefix) && tagVal.endsWith(this.placeholderSuffix)) {
                            // 去除${}
                            tagVal = tagVal.substring(tagVal.lastIndexOf(this.placeholderPrefix) + 2, tagVal.lastIndexOf(this.placeholderSuffix));
                            infos.add(new PropertyInfo(pv.getName(), tagVal));
                        }
                    }
                }
                initBeanPropertyMap();
                if (infos.size() > 0)
                    beanPropertyMap.put(curName, infos);
            }
        }
        getConfigContext().setAttribute(Constants.APPLICATION_CONTEXT, applicationContext);
        getConfigContext().setAttribute(Constants.BEAN_PROPERTY_MAP, beanPropertyMap);
        super.doProcessProperties(beanFactoryToProcess, valueResolver);
    }

    private void initBeanPropertyMap() {
        Object o = getConfigContext().getAttribute(Constants.BEAN_PROPERTY_MAP);
        if (o == null) {
            beanPropertyMap = new ConcurrentHashMap<>();
        } else
            beanPropertyMap = (Map<String, List<PropertyInfo>>) o;
    }

    public ConfigContext getConfigContext() {
        if (applicationContext == null) {
            throw new IllegalStateException();
        }
        return (ConfigContext) ((WebApplicationContext) applicationContext).getServletContext().getAttribute(Constants.CONFIG_CONTEXT);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public String resolvePlaceholder(String placeholder, Properties props) {

        try {
            Object value = props.get(placeholder);
            if (value == null) {
                return getConfigContext().getConfigClient().getValue(placeholder);
            }
            return value.toString();
        } catch (ConfigurationException e) {
            throw new BeanCreationException("Failed to resolve property, key: " + placeholder, e);
        }
    }

    class PropertyInfo {

        /**
         * 字段名
         */
        private String fieldName;

        /**
         * 字段对应 key名称
         * ${key} - key
         */
        private String keyName;

        public PropertyInfo(String fieldName, String keyName) {
            this.fieldName = fieldName;
            this.keyName = keyName;
        }

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getKeyName() {
            return keyName;
        }

        public void setKeyName(String keyName) {
            this.keyName = keyName;
        }
    }
}
